import { h, fragment } from '@knno/dom/jsx';
import { Nodes } from '@knno/dom';
import { PopList } from '@knno/ui/popList';
import { Icon } from '@knno/ui/jsx/icon';
import { Dialog } from '@knno/ui/dialog';
import { Input } from '@knno/ui/jsx/input';
import { Button, UIButton } from '@knno/ui/jsx/button';
import { Progress } from '../components/progress/progress';
import { Toast } from '../components/toast/toast';
import { DICT } from '../dict';
import { getLanguage } from '@knno/i18n';
import { renderIf } from '../jsx/utils';
import { PopupPosition } from '../components/popup/popup';

export function mergeClass(cls: string | string[], classes?: string | string[]): string[] {
	return [
		...(cls instanceof Array ? cls : [cls]),
		...(classes instanceof Array ? classes : typeof classes === 'string' ? [classes] : []),
	];
}

type MenuItem = {
	icon?: string;
	value: string;
} & Record<string, unknown>;

type Pos =
	| {
			x: number;
			y: number;
	  }
	| PopupPosition;

type Callback = (selected: string, selectedItem: MenuItem) => void;

type PopupMenuOption = {
	refer?: Nodes;
	menu: MenuItem[];
	pos?: Pos;
	width?: number | string;
	maxShowLines?: number;
};

export function popupMenu(options: PopupMenuOption, callback: Callback) {
	PopList.show(
		options.menu,
		{
			text: 'value',
			value: 'value',
			formatter(row) {
				const r = row as MenuItem;
				return (
					<span style={{ display: 'flex', alignItems: 'center' }}>
						{renderIf(typeof r.icon === 'string', () => (
							<Icon icon={r.icon as string} style={{ flexShrink: 0, width: '1em', height: '1em' }} />
						))}
						<span style={{ marginLeft: '0.5em' }}>{r.value}</span>
					</span>
				);
			},
		},
		{
			refer: options.refer,
			focus: true,
			width: options.width,
			maxShowLines: options.maxShowLines,
			popupPosition: options.pos ? options.pos : 'bottom-right',
		}
	).onSelect((value, text, row) => {
		callback(value + '', options.menu[row]);
	});
}

export function askInsertTable(callback: (rows: number, cols: number) => void) {
	let rows: Nodes;
	let cols: Nodes;
	const dlg = Dialog.create(
		<div
			style={{
				textAlign: 'left',
				display: 'grid',
				gridTemplateColumns: getLanguage().startsWith('zh') ? '1fr 2fr' : '1fr',
				width: '16em',
				alignItems: 'center',
				rowGap: '0.5em',
			}}
		>
			<label>{DICT.rowCount}</label>
			<Input type="number" value="3" min={1} max={100} ref={(i) => (rows = i)} style={{ width: 'auto' }} />
			<label>{DICT.columnCount}</label>
			<Input type="number" value="4" min={1} max={10} ref={(i) => (cols = i)} style={{ width: 'auto' }} />
		</div>
	)
		.title(DICT.insertTable)
		.escClose(true)
		.autoClose(true)
		.buttons(
			(
				<Button
					type="primary"
					onclick={() => {
						if (!/^\d+$/.test(rows.text())) {
							Dialog.error(DICT.invalidRowCount);
							return;
						}
						const rowCount = parseInt(rows.text());
						if (rowCount < 1 || rowCount > 500) {
							Dialog.error(`${DICT.invalidRowCount} ${DICT.shouldBeBetween} 1 ~ 500`);
							return;
						}

						if (!/^\d+$/.test(cols.text())) {
							Dialog.error(DICT.invalidColumnCount);
							return;
						}
						const colCount = parseInt(cols.text());
						if (colCount < 1 || colCount > 10) {
							Dialog.error(`${DICT.invalidColumnCount} ${DICT.shouldBeBetween} 1 ~ 10`);
							return;
						}
						dlg.close();
						callback(rowCount, colCount);
					}}
				>
					{DICT.ok}
				</Button>
			) as UIButton
		)
		.show();
}

function checkFiles(fileList: FileList, mimeType: string) {
	if (fileList.length == 0) {
		return false;
	}
	const f = fileList[0];
	if (/^image\/png/.test(mimeType)) {
		if (!/\.png$/.test(f.name)) {
			return false;
		}
	} else if (/^image\/jpeg/.test(mimeType)) {
		if (!/\.jpe?g$/.test(f.name)) {
			return false;
		}
	} else if (/^image\/gif/.test(mimeType)) {
		if (!/\.gif$/.test(f.name)) {
			return false;
		}
	} else if (/^image\/svg\+xml/.test(mimeType)) {
		if (!/\.svg$/.test(f.name)) {
			return false;
		}
	} else if (/^image\/\*/.test(mimeType)) {
		if (!/\.(png|jpe?g|gif|svg)$/.test(f.name)) {
			return false;
		}
	} else if (/^video\/(\*|mp4)/.test(mimeType)) {
		if (!/\.(mp4)$/.test(f.name)) {
			return false;
		}
	} else if (/^audio\/(\*|mp3|mpeg)/.test(mimeType)) {
		if (!/\.(mp3)$/.test(f.name)) {
			return false;
		}
	}
	return true;
}

interface UploadInfo {
	name: string;
	size: number;
	path: string;
}
function upload(url: string, fileList: FileList, extraData?: Record<string, string>): Promise<UploadInfo> {
	return new Promise<UploadInfo>((resolve, reject) => {
		Progress.show();
		const fd = new FormData();
		for (const file of fileList) {
			fd.append('file', file, encodeURIComponent(file.name));
		}
		if (extraData) {
			for (const key in extraData) {
				if (Object.hasOwn(extraData, key)) {
					fd.append(key, extraData[key]);
				}
			}
		}
		const xhr = new XMLHttpRequest();
		xhr.upload.addEventListener(
			'progress',
			(e) => {
				Progress.update(e.loaded / e.total, DICT.uploading);
			},
			false
		);
		xhr.addEventListener(
			'load',
			() => {
				if (xhr.status != 200) {
					Progress.close();
					reject(xhr.response || xhr.statusText || xhr.status);
				} else {
					Progress.close();
					resolve(JSON.parse(xhr.response));
				}
			},
			false
		);
		xhr.addEventListener(
			'error',
			() => {
				Progress.close();
				reject(DICT.networkError);
			},
			false
		);
		xhr.addEventListener(
			'abort',
			() => {
				Progress.close();
				reject(DICT.aborted);
			},
			false
		);
		try {
			xhr.open('POST', url);
			xhr.send(fd);
		} catch (e) {
			Progress.close();
			reject(e + '');
		}
	});
}

export function showUploadNoUI(uploadURL: string, mimeType = '*/*', dataURL?: boolean): Promise<string | null> {
	return new Promise<string | null>((resolve) => {
		const ipt = document.createElement('input');
		ipt.type = 'file';
		ipt.accept = mimeType;
		ipt.click();
		ipt.onchange = () => {
			const fileList = ipt.files;
			if (fileList && fileList.length > 0) {
				if (!checkFiles(fileList, mimeType)) {
					resolve(null);
					return;
				}
				if (dataURL) {
					// only return first item
					const reader = new FileReader();
					reader.addEventListener(
						'load',
						() => {
							// convert image file to base64 string
							resolve(reader.result as string | null);
						},
						false
					);
					reader.readAsDataURL(fileList[0]);
				} else {
					upload(uploadURL, fileList)
						.then((info) => {
							resolve(info.path);
						})
						.catch((reason) => {
							Toast.show(reason);
							resolve(null);
						});
				}
			} else {
				resolve(null);
			}
		};
	});
}

export function askInsertImage(callback: (src: string) => void) {
	showUploadNoUI('', 'image/*', true).then((value) => {
		if (value) {
			callback(value);
		}
	});
}

export async function delay(fn: () => void, timeout: number): Promise<void> {
	return new Promise((resolve) => {
		setTimeout(() => {
			fn();
			resolve();
		}, timeout);
	});
}

export type KeyboardEventInit = {
	key: string;
	location?: number;
	code?: string;
	isComposing?: boolean;
	ctrlKey?: boolean;
	shiftKey?: boolean;
	altKey?: boolean;
	metaKey?: boolean;
};

export function sendKeyboardEvent(el: HTMLElement | Nodes, eventType: string, detail: KeyboardEventInit) {
	const e = new KeyboardEvent(eventType, detail);
	(el instanceof Array ? el[0] : el).dispatchEvent(e);
}
